/**
 *
 */
package asm16;

import java.io.*;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.TreeMap;
import java.util.ArrayList;

/**
 * The main assembler class.
 * Takes an input file name as a parameter. Creates an output file with the
 * same file name but a .out extension.
 * @author Piotr
 *
 */
public class Assembler {

        private static File sourceFile;
        private static File outputFile;
        private static PrintWriter outputWriter;
        private static TreeMap<String, Integer> symbolTable;
        private static ArrayList<Object[]> branchTable;
        private static TreeMap<String, String> opcodeTable;
        private static TreeMap<String, String> functTable;
        private static TreeMap<String, String> alter1Table;
        private static TreeMap<String, Integer> regAliasTable;
        private static ArrayList<String> outputText;
       
        /**
         * Main function. Takes input file name as parameter.
         * @param args The input file name.
         */
        public static void main(String[] args) throws IOException {

                initTables();
               
       
                // Try to open the source file
                if (args.length > 0) {
                        sourceFile = new File(args[0]);
                        if (!sourceFile.exists()) {
                                System.out.println("File \"" + args[0] + "\" does not exist");
                                System.exit(1);
                        }
         
                createOutputFile(args[0]);
                translateFile();
                writeOutputFile();              //Throws IOException
                       
                outputWriter.close();   //Throws IOException
                } else {
                        System.out.println("No input file specified");
                        System.exit(1);
                }
        }
       




        /**
         * Writes the contents of the outputText array out to the output file.
         * @throws IOException
         */
        private static void writeOutputFile() throws IOException {
                int lineNum = 0;
                for (String line: outputText ) {
                        outputWriter.println(lineNum + "\t" + line);
                        lineNum++;
                }
                outputWriter.flush();
        }






        /**
         * Attempts to create an output file with the same name as the input file
         * but with a .out extension. (stripping the original extension)
         */
        private static void createOutputFile(String inputFileName) {
                String outputFileName;
                //Scan the input file name from the end, to identify the extension
                //Assume a blank extension if the beginning of the string is reached
                //or a pathname delimiter.
                int ii = inputFileName.length();
                char ch;
                do {
                        ii--;
                        ch = inputFileName.charAt(ii);
                } while (ii > 0 && ch != '.' && ch != '/' && ch != '\\');
                if (ii == 0 || ch == '/' || ch == '\\') {
                        outputFileName = inputFileName + ".out";
                } else {
                        outputFileName = inputFileName.substring(0, ii) + ".out";
                }
                //System.out.println(outputFileName);
                outputFile = new File(outputFileName);
                try {
                        outputWriter = new PrintWriter(outputFile);
                } catch (IOException e) {
                        System.out.println("Error creating output file \"" + outputFileName + "\"");
                        System.exit(1);
                }
        }

        /**
         * Subroutine that performs the overall function of translating the input file
         * in assembly to an output file in machine code.
         */
        private static void translateFile() throws IOException {
                symbolTable = new TreeMap<String, Integer>();
                branchTable = new ArrayList<Object[]>();
                outputText = new ArrayList<String>();
                //Read input file in line-by-line, create output file and branch/symbol tables
                doFirstPass();        
                doSecondPass();
               
        }
       
    /**
     * Method to perform the second pass of the assembler.
     * Iterates through the branch table, calculates the branch targets using
     * the symbol table, and fills in the target addresses.
     */
    private static void doSecondPass() {
               
	String code="";
	String sub="";
	String offsetStr="";
        
	for (Object[] entry: branchTable) {
	    
	    int source = (Integer)entry[0];
	    String label = (String)entry[1];                        
	    if (symbolTable.containsKey(label)) {
		int target = symbolTable.get(label);                                
		int offset = target - (source + 1);                                
		offsetStr = toBits(offset, 21, true);
		if (offsetStr == null) {
		    System.out.println("Error: Label \"" + label +
				       "\" out of range.");
		}                   
		sub= offsetStr.substring(0,5);
		code = outputText.get(source);
		code = code.replace("#target#", sub);                             
		outputText.set(source, code);
		code=offsetStr.substring(5,21);
		outputText.add(source+1, code);     
		
	    } else {
		System.out.println("Error: Label \"" + label +
				   "\" not found.");
	    }        
	}
        
    }






        /**
         * Subroutine to do the first pass of the assembler.
         * @return      The number of lines in the output file.
         * @throws FileNotFoundException
         */
        private static int doFirstPass() throws FileNotFoundException {
                Scanner scanIn = new Scanner(sourceFile);
                String line;
                int inLineCount = 0;
                int outLineCount = 0;
                while (scanIn.hasNext()) {
                        line = scanIn.nextLine();
                        inLineCount++;
                        outLineCount = doFirstPassLine(inLineCount, outLineCount, line);
                }
                return outLineCount;
        }
       
        /**
         * Subroutine to do the first-pass processing on a line from the input file
         * @param inLineCout The current line of the input file
         * @param outLineCount The current line count in the output file
         * @param line The String of the line from the input to be processed
         * @return lineCount incremented by the number of lines that were added to the output
         */
        private static int doFirstPassLine(int inLineCnt, int lineCount, String line) {
                Scanner scanLine = new Scanner(line);
                String token;
                if (!scanLine.hasNext()) {
                        return lineCount;       //The string is empty
                } else {
                        //Check the beginning of a line for a label
                        //Any word followed by a colon found at the beginning of the line is
                        //treated as a label.
                        if ((token = scanLine.findInLine("^\\s*\\w+\\s*:")) != null) {
                                token = token.split(":")[0];
                                token = token.trim();
                                //System.out.println("Found label: " + token);
                                //Check if the label is a mnemonic
                                if (opcodeTable.containsKey(token.toLowerCase())) {
                                        System.out.println("Syntax error: Line " + inLineCnt +
                                                        " Invalid label.");
                                        System.exit(1);
                                }
                                //Add label to the symbol table
                                if (!symbolTable.containsKey(token)) {
                                        symbolTable.put(token, lineCount);
                                } else {
                                        System.out.println("Syntax error: Line " + inLineCnt +
                                        " Invalid label reuse.");
                                        System.exit(1);
                                }
                        }
                        //Check any following token for a mnemonic
                        if (scanLine.hasNext()) {
                                token = scanLine.next();
                                //System.out.println(token);
                                if (opcodeTable.containsKey(token.toLowerCase())) {
                                        int jj;
                                        jj = doMnemonicTranslation(inLineCnt, lineCount, token.toLowerCase(),
                                                        scanLine, line);
                                        return lineCount + jj;
                                } else {
                                        //Check for comments, else print error
                                        if (token.startsWith(";") || token.startsWith("//")) {
                                                //it's a comment, ignore it
                                        } else {
                                                System.out.println("Syntax error: Line " + inLineCnt +
                                                " Mnemonic, comment, or label expected.");
                                                System.exit(1);
                                        }
                                }
                        }
                       
                }
                return lineCount;
        }




        /**
         * Helper method to translate a single line, given a valid mnemonic and the scanner
         * pointing after the mnemonic. Adds a line to the outputText, and fills in the
         * branchTable if necessary.
         * @param lineNum The line number of the output file (address)
         * @param mnemonic
         * @param scanLine Scanner pointing after the mnemonic
         * @return returns the number of output lines created.
         */
        private static int doMnemonicTranslation(int inLnCt, int lineNum, String mnemonic,
                                                        Scanner scanLine, String line) {
                String mn = mnemonic;
                String rs;
                String rt;
                String rd;
               
                int len, x=0;
                //int offset;
                //String target;
                //String comment = "";
                String outCode = "";
                int numOutLines = 0;
                //System.out.println(mn);
                try {
                        if (mn.equals("add") || mn.equals("sub") || mn.equals("and") || mn.equals("or")
                            || mn.equals("nor")|| mn.equals("mul") || mn.equals("mv")) {  // All 3 operand instructions
                                //System.out.println("Caught mnemonic");
                                String[] temp = scanLine.nextLine().split(",|//|;",3);
                                //System.out.println("temp.length " + temp.length);
                                if (temp.length < 2) {
                                        throw new NoSuchElementException();
                                }
                                rd = temp[0].trim();
                                rs = temp[1].trim();
                               
                                //comment = (temp.length >= 4 ? temp[3] : "").trim();
                                //System.out.println("rd " + rd);
                                //System.out.println("rs " + rs);
                                //System.out.println("rt " + rt);
                                //System.out.println("rem " + comment);
                                outCode = opcodeTable.get(mn)
                                             + toBits(regAliasTable.get(rd), 5, false)
                                             + toBits(regAliasTable.get(rs), 5, false) ;
                                //System.out.println(outCode);
                                numOutLines = 1;
                        }                                                                                        
                       
                        else if (mn.equals("sll") || mn.equals("srl") || mn.equals("sra")
                           ||mn.equals("sllv") || mn.equals("srlv") || mn.equals("srav")) {
                                String[] temp = scanLine.nextLine().split(",|//|;",3);
                                //System.out.println("temp.length " + temp.length);
                                if (temp.length < 2) {
                                        throw new NoSuchElementException();
                                }
                                rd = temp[0].trim();
                                rs = temp[1].trim();
                               
                                //comment = (temp.length >= 4 ? temp[3] : "").trim();
                                //System.out.println("rd " + rd);
                                //System.out.println("rs " + rs);
                                //System.out.println("rt " + rt);
                                //System.out.println("rem " + comment);
                                if (mn.equals("sll") || mn.equals("srl") || mn.equals("sra")) { 
				    if(mn.equals("sra"))        
					rs = toBits((new Integer(rs)), 5, true);
				    else
					rs = toBits((new Integer(rs)), 5, false);
				    if (rs == null) {
					System.out.println("Error: Line " + inLnCt + ", value out of range.");
				    }                                
				    outCode = opcodeTable.get(mn)
					+ toBits(regAliasTable.get(rd), 5, false)   
					+ rs ;
                                }
                               
				// sllv, slav, srav Instructions...                            
                                //else {
				else{
				    outCode = opcodeTable.get(mn)
					+ toBits(regAliasTable.get(rd), 5, false)
					+ toBits(regAliasTable.get(rs), 5, false);     
				}
                               
 
				//System.out.println(outCode);
                                numOutLines = 1;
                        }
                       
                               
                               
                               
                        else if (mn.equals("addi")) {
                                String[] temp = scanLine.nextLine().split(",|//|;",3);
                                //System.out.println("temp.length " + temp.length);
                                if (temp.length < 2) {
                                        throw new NoSuchElementException();
                                }
                               
                                rd = temp[0].trim();
                                rs = temp[1].trim();
                                //comment = (temp.length >= 3 ? temp[2] : "").trim();
                                //System.out.println("rd " + rd);
                                //System.out.println("rs " + rs);
                                //System.out.println("rt " + rt);
                                //System.out.println("rem " + comment);
                               
                                                                       
                                rs = toBits(new Integer(rs), 8, true);
                                if (rs == null) {
                                        System.out.println("Error: Line " + inLnCt + ", value out of range.");
                                   }                                                                                            
                               
                                outCode = opcodeTable.get(mn)+  
                                             toBits(regAliasTable.get(rd), 5, false)
                                           + rs;
                                //System.out.println(outCode);
                                numOutLines = 1;
                        } else if (mn.equals("li")) {
                            String[] temp = scanLine.nextLine().split(",|//|;",3);
                            //System.out.println("temp.length " + temp.length);
                            if (temp.length < 2) {
                                    throw new NoSuchElementException();
                            }
                            rd = temp[0].trim();
                            rs = temp[1].trim();
                            //comment = (temp.length >= 3 ? temp[2] : "").trim();
                            //System.out.println("rd " + rd);
                            //System.out.println("rs " + rs);
                            //System.out.println("rt " + rt);
                            //System.out.println("rem " + comment);
                            rt="";
                            len = determinelength(rs,x) ;
                            if(len==0)
				System.out.println("Error: Line " + inLnCt + ", value out of range.");
			    rs = toBits(new Integer(rs), len, true);
                            if (rs == null) {
				System.out.println("Error: Line " + inLnCt + ", value out of range.");
			    }
                            //System.out.println("li len is " + len+" rs "+ rs);
			    
                            switch(len) {
                            case 5: {
				outCode=gencode(len, inLnCt, rs, rd,rt, mn,line) ;
				numOutLines = 1;
				//System.out.println("entering 5");
				break;
                            }
                            case 21: {  
                                outCode=gencode(len, inLnCt, rs, rd,rt, mn,line) ;
				numOutLines = 2;
				//System.out.println("entering switch case");
				break;
                            }
                            case 32: {
				
                                outCode=gencode(len, inLnCt, rs, rd,rt, mn,line) ;
				numOutLines = 3;
				//System.out.println("entering 32");
			    }      
               
                }
               
               
                            //System.out.println(outCode);
                           
                    }
                       
                       
                         else if (mn.equals("load") || mn.equals("store")) {
                                String[] temp = scanLine.nextLine().split(",",2);
                                x=1;
                                if (temp.length < 2) {
                                        throw new NoSuchElementException();
                                }
                                rd = temp[0].trim();
                                temp = temp[1].split("\\(|\\)|//|;");
                                if (temp.length < 2) {
                                        throw new NoSuchElementException();
                                }
                                rs = temp[0].trim();
                                rt = temp[1].trim();                                
                               // System.out.println("rs = " + rs) ;
                                len = determinelength(rs,x) ;
                                if(len==0)
				    System.out.println("Error: Line " + inLnCt + ", value out of range.");
                               
                                rs = toBits(new Integer(rs), len, true);
                                if (rs == null) {
				    System.out.println("Error: Line " + inLnCt + ", value out of range.");
                                }
                               
                                if(rs.equals("0")){
                                outCode = opcodeTable.get(mn)
                                + toBits(regAliasTable.get(rd), 5, false)
                                + toBits(regAliasTable.get(rt), 5, false) ;
                               
                                numOutLines = 1;
                                } else {
                               
                                switch(len) {                              
                                case 1:{
                                    outCode = opcodeTable.get(mn)
                                    + toBits(regAliasTable.get(rd), 5, false)
                                    + toBits(regAliasTable.get(rt), 5, false) ;
                                   
                                    numOutLines = 1;
                                    break;
                                    }
                                       
                               
                                case 16: {      
                                        outCode=gencode(len, inLnCt, rs, rd,rt, mn,line) ;
                                   numOutLines = 2; break;
                                }
                                case 32: {      
                                        outCode=gencode(len, inLnCt, rs, rd,rt, mn,line) ;
                                       numOutLines = 3; break;
                                    }                                                                                                                                
                               
                                }
                                             
                          }    //System.out.println(outCode);
                               
			 } else if (mn.equals("bez") || mn.equals("blz")||mn.equals("blez")
				    ||mn.equals("bgz")||mn.equals("bnez")) {
			     String[] temp = scanLine.nextLine().split(",|//|;",3);
			     //System.out.println("temp.length " + temp.length);
			     if (temp.length < 2) {
				 throw new NoSuchElementException();
			     }
			     rs = temp[0].trim();
			     rd = temp[1].trim();
			     //comment = (temp.length >= 3 ? temp[2] : "").trim();
			     //System.out.println("rd " + rd);
			     //System.out.println("rs " + rs);
			     //System.out.println("rt " + rt);
			     //System.out.println("rem " + comment);
                             
			     outCode = functTable.get(mn)
				 + toBits(regAliasTable.get(rs), 5, false) + "#target#";               
			     Object[] bTableE = {new Integer(lineNum), rd};
			     branchTable.add(bTableE);
			     //System.out.println(outCode);
			     numOutLines = 2;
			 }
			
                       
                       
                        else { // mn must be jalr
                                String[] temp = scanLine.nextLine().split(",|//|;",3);
                                //System.out.println("temp.length " + temp.length);
                                if (temp.length < 2) {
                                        throw new NoSuchElementException();
                                }
                                rd = temp[0].trim();
                                rs = temp[1].trim();
                                //comment = (temp.length >= 3 ? temp[2] : "").trim();
                                //System.out.println("rd " + rd);
                                //System.out.println("rs " + rs);
                                //System.out.println("rt " + rt);
                                //System.out.println("rem " + comment);
                                outCode = opcodeTable.get(mn)
                                             + toBits(regAliasTable.get(rd), 5, false)
                                             + toBits(regAliasTable.get(rs), 5, false) ;
                                           
                                //System.out.println(outCode);
                                numOutLines = 1;
                        }
                        outputText.add(outCode + "\t" + line.trim() );
                } catch (NoSuchElementException e) {
                        System.out.println("Syntax error: Line " + inLnCt + ", operand expected.");
                        System.exit(1);
                } catch (NullPointerException e) {
                        System.out.println("Syntax error: Line " + inLnCt + ", operand error.");
                        System.exit(1);
                }
                return numOutLines;
               
        }
       
       
        /**
         * Helper method. Creates a string of bits given an integer and the length.
         * Either sign-extends the string.
         * @param integer The number to be converted to bits
         * @param length The length of the string to be created
         * @param signed If true, the number will be sign-extended
         * @return Returns a null if it cannot be represented in the specified length
         */
        private static String toBits(Integer integer, int length, boolean signed) {
                String out = Integer.toBinaryString(integer);
                //if(length==16) System.out.println("integ: "+ integer) ;
                if (signed) {
                        if (integer < 0) {
                                if (integer < (-1 * (int)Math.pow(2, (length - 1)))) {
                                        return null;
                                } else {
                                        out = out.substring(out.length() - length);
                                }
                        } else {        //Positive signed
                                if (integer > (int)Math.pow(2,(length - 1)) - 1) {
                                        return null;
                                } else {
                                        out = charSeq('0', length-out.length()) + out;
                                }
                        }
                } else { //Unsigned
                        if (integer < 0 || integer > (int)Math.pow(2,(length)) - 1) {
                                return null;
                        } else {
                                out = charSeq('0', length-out.length()) + out;
                        }
                }
                return out;
        }
       
        /**
         * Returns a string composed of ch repeated num times
         * @param ch
         * @param num
         */
       
       
        private static int determinelength(String x, int f) {
	    int no = Integer.parseInt(x) ;
	    int length;
	    if(f==1) {
                length=1;
                if (no==0)        
                    return length;
		length =16;
		if (no> -Math.pow(2, length-1) && no<=Math.pow(2, length-1))                                     
		    return length;
		length =32;                                            
		if (no> -Math.pow(2, length-1) && no<=Math.pow(2, length-1))        
		    return length;                                            
		
	    }                
                        
	    length =5;    
	    if (no> -Math.pow(2, length-1) && no<=Math.pow(2, length-1))        
		return length;
	           
	    length= 21;
	    if (no> -Math.pow(2, length-1) && no<=Math.pow(2, length-1))        
		return length;
	    
	    length= 32;
	    if (no> -Math.pow(2, length-1) && no<=Math.pow(2, length-1))        
		return length;
	    	    
	    return 0;
	    
        }
       
       
    private static String gencode(int len, int inLnCt, String rs, String rd, String rt,
				  String mn, String line) {
        String outCode="" ;            
       
        if(mn.equals("load")||mn.equals("store")) {
               
	    switch(len){
	    case 16:{
       
		outCode = functTable.get(mn) +toBits(regAliasTable.get(rd), 5, false)
		    +toBits(regAliasTable.get(rt), 5, false) ;    
		outputText.add(outCode + "\t" + line.trim() );
		outCode=rs;
		return outCode ;              
		
                       
	    }      
	    case 32:{
		outCode = alter1Table.get(mn) +toBits(regAliasTable.get(rd), 5, false)
		    +toBits(regAliasTable.get(rt), 5, false) ;      
		outputText.add(outCode + "\t" + line.trim() );
		outCode=rs.substring(0,16);
		outputText.add(outCode + "\t" + line.trim() );
		outCode=rs.substring(16,32);
		return outCode ;

		
                       
	    }
		
	    }
	}      
	else{                                
	    
	    if(len==0){
                System.out.println("Error: Line " + inLnCt + ",imm value out of range.");
		
	    }
	    else if(len==5)  {                                                                                                                      
		outCode =    opcodeTable.get(mn) +toBits(regAliasTable.get(rd), 5, false)+rs ;
		return outCode ;
             
	    } else if(len==21) {                                                    
		
		outCode = functTable.get(mn) +toBits(regAliasTable.get(rd), 5, false)
		    +rs.substring(0,5) ;
                outputText.add(outCode + "\t" + line.trim() );
		//System.out.println("entering gencode");
		outCode = rs.substring(5,21) ;
		return outCode ;
                 
                 
	    } else if(len==32) {
                         
		outCode = alter1Table.get(mn) +toBits(regAliasTable.get(rd), 5, false)
		    + "00000" ;
		outputText.add(outCode + "\t" + line.trim() );
		
		outCode = rs.substring(0,16) ;
		outputText.add(outCode + "\t" + line.trim() );
         
         
		outCode = rs.substring(16,32) ;
		return outCode ;      
	    }
           
	}    
	return "0";    
    }
       
       
       
               
               
       
        private static String charSeq(char ch, int num) {
                String out = "";
                for (int ii = 0 ; ii < num ; ii++) {
                        out = out + ch;
                }
                return out;
        }



       
        /**
         * Fills in the tables for the opcodes and register aliases
         */
        private static void initTables() {
                opcodeTable = new TreeMap<String, String>();
                regAliasTable = new TreeMap<String, Integer>();
                alter1Table = new TreeMap<String, String>();
                functTable = new TreeMap<String, String>();
                // Add supported instructions here. First field is opcode, second field is funct
                opcodeTable.put("add",  "000000");
                opcodeTable.put("sub",  "000001");
                opcodeTable.put("and",  "000010");
                opcodeTable.put("or",   "000011");
                opcodeTable.put("nor",  "000100");
                opcodeTable.put("mul",  "000101");
                opcodeTable.put("sll",  "000110");
                opcodeTable.put("srl",  "000111");
                opcodeTable.put("sra",  "001000");
                opcodeTable.put("sllv", "001001");
                opcodeTable.put("srlv", "001010");
                opcodeTable.put("srav", "001011");                              
                opcodeTable.put("li",   "011000");
                opcodeTable.put("addi", "010");                
                opcodeTable.put("load", "011001");
                opcodeTable.put("store","011010");              
                opcodeTable.put("bez",  "011011");
                opcodeTable.put("blz",  "011100");
                opcodeTable.put("blez", "011101");
                opcodeTable.put("bgz",  "011110");                
                opcodeTable.put("bnez", "011111");      
                opcodeTable.put("jalr", "001100");
                opcodeTable.put("nop",  "000000");
			opcodeTable.put("mv",   "001101");
               
                // funct table for all the mnemonics
               
                functTable.put("li","100000") ;
                functTable.put("load","100001") ;
                functTable.put("store","100010") ;
                functTable.put("bez",   "100011");
                functTable.put("blz",   "100100");
                functTable.put("blez",  "100101");
                functTable.put("bgz",   "100110");                
                functTable.put("bnez",  "100111");      
               
               
               
               // alter1 table.. for 32 bit imm valued mnemonics..
                alter1Table.put("li","101000") ;
                alter1Table.put("load","101001") ;
                alter1Table.put("store","101010") ;
                alter1Table.put("bez",   "101011");
                alter1Table.put("blz",   "101100");
                alter1Table.put("blez",  "101101");
                alter1Table.put("bgz",   "101110");                
                alter1Table.put("bnez",  "101111");      
               
               
               
               
               
               
                // All aliases for registers
                regAliasTable.put("r0", 0);
                regAliasTable.put("r1", 1);
                regAliasTable.put("r2", 2);
                regAliasTable.put("r3", 3);
                regAliasTable.put("r4", 4);
                regAliasTable.put("r5", 5);
                regAliasTable.put("r6", 6);
                regAliasTable.put("r7", 7);
                regAliasTable.put("r8", 8);
                regAliasTable.put("r9", 9);
                regAliasTable.put("r10", 10);
                regAliasTable.put("r11", 11);
                regAliasTable.put("r12", 12);
                regAliasTable.put("r13", 13);
                regAliasTable.put("r14", 14);
                regAliasTable.put("r15", 15);
                /*regAliasTable.put("r16", 16);
                regAliasTable.put("r17", 17);
                regAliasTable.put("r18", 18);
                regAliasTable.put("r19", 19);
                regAliasTable.put("r20", 20);
                regAliasTable.put("r21", 21);
                regAliasTable.put("r22", 22);
                regAliasTable.put("r23", 23);
                regAliasTable.put("r24", 24);
                regAliasTable.put("r25", 25);
                regAliasTable.put("r26", 26);
                regAliasTable.put("r27", 27);
                regAliasTable.put("r28", 28);
                regAliasTable.put("r29", 29);
                regAliasTable.put("r30", 30);
                regAliasTable.put("r31", 31);
*/              
        }

       
       
}

